﻿///<FileHeader>
///     <FileName>StringConverter.cs</FileName>
///     <Author>Kiran Chand Palakkattiri</Author>
///     <Project>
///         <Name>Property Visibility Extensions for DSL</Name>
///         <Url>http://kiranchand.codeplex.com</Url>
///     </Project>
///     <CreationDate>October 12, 2009.</CreationDate>
///     <Contents>
///         <Content type="class" name="ExtendedStringConverter"
///             comments="Collection provider for extended string converter"
///             baseclass="StringConverter"/>
///     </Contents>
///     <Dependencies>
///         <Dependency type="reference" namespace="System" />
///         <Dependency type="reference" namespace="System.Collections.Generic" />
///         <Dependency type="reference" namespace="System.ComponentModel" />
///         <Dependency type="reference" namespace="System.Linq" />
///         <Dependency type="reference" namespace="Microsoft.VisualStudio.Modeling"
///             assembly="Microsoft.VisualStudio.Modeling.Sdk" version="9.0.0.0" prerequisites="Visual Studio SDK 1.1"/>
///         <Dependency type="reference" namespace="Microsoft.VisualStudio.Modeling.Design"
///             assembly="Microsoft.VisualStudio.Modeling.Sdk" version="9.0.0.0" prerequisites="Visual Studio SDK 1.1" />
///         <Dependency type="reference" namespace="Microsoft.VisualStudio.Modeling.Diagrams"
///             assembly="Microsoft.VisualStudio.Modeling.Sdk.Diagrams" version="9.0.0.0" prerequisites="Visual Studio SDK 1.1" />
///     </Dependencies>
///     <Modifications>
///         <Modification changeset="1006" date="April 08, 2010" author="Kiran Chand Palakkattiri">
///             <Target="function" targetname="ReflectContextInfo(ITypeDescriptorContext context)" type="create"
///                 comment="added function to reflect the context information for MergedPropertyDescriptor
///                 supports multiple selection"/>
///             <Target="function" targetname="GetContextInfo(...)" type="update"
///                 comment="changed the return type to Dictionary<IStandardValueProvider,Guid>
///                 for supporting context with multiple selection;
///                 removed all the parameters;
///                 added an instance of PropertyDescriptor as input parameter;
///                 written reflection algorithm to find out the domain property id and
///                 IStandardValueProvider for MergedPropertyDescriptor;"/>
///             <Target="function" targetname="GetStandardValuesExclusiveFromContext(...)" type="update"
///                 comment="added support for querying and merging results from multiple standard value providers"/>
///             <Target="function" targetname="GetStandardValuesFromContext(...)" type="update"
///                 comment="added support for querying and merging results from multiple standard value providers"/>
///             <Target="function" targetname="GetIsValidFromContext(...)" type="update"
///                 comment="added support for querying and merging results from multiple standard value providers"/>
///             <Target="function" targetname="GetStandardValuesSupportedFromContext(...)" type="update"
///                 comment="added support for querying and merging results from multiple standard value providers"/>
///             <Target="class" targetname="Extensions" type="create"
///                 comment="created new static class with extension method."/>
///         </Modification>
///         <Modification changeset="1008" date="April 26, 2010" author="Kiran Chand Palakkattiri">
///             <Target="function" targetname="GetStandardValuesFromContext(...)" type="update"
///                 comment="added provision to perform set operations on merging
///                 the standard value collection using union or intersection
///                 when queried from different standard value providers."/>
///         </Modification> 
///     </Modifications>
///     <Version>3.0.0.0</Version>
///</FileHeader>

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Microsoft.VisualStudio.Modeling.Design;

namespace Kiranchand.Dsl.Extensions.PropertyVisibility
{
    /// <summary>
    /// A reusable string converter for populating the collection of
    /// strings in property grid
    /// </summary>
    public sealed class ExtendedStringConverter : StringConverter
    {
        /// <summary>
        /// Returns whether the object supports a standard set of values that can be picked up from the list.
        /// </summary>
        /// <param name="context">instance of the ITypeDescriptorContext</param>
        /// <returns></returns>
        public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
        {
            // query whether standard values are supported or not.
            return this.GetStandardValuesSupportedFromContext(context);
        }

        /// <summary>
        ///  Returns a whether the collection of standard values provided from the
        ///  default context is an exclusive list or not.
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
        {
            // query whether the given collection is an exclusive collection or not.
            return this.GetStandardValuesExclusiveFromContext(context);
        }

        /// <summary>
        /// Returns a collection of standard values from the default context for the
        ///  data type this type converter is designed for.
        /// </summary>
        /// <param name="context">instance of the ITypeDescriptorContext</param>
        /// <returns> a standard set of valid values, or null if the data type does not support
        /// a standard set of values.
        /// </returns>
        public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
        {
            // Query standard values from the context
            return this.GetStandardValuesFromContext(context);
        }

        /// <summary>
        /// Returns whether the given value object is valid for this type and for the
        /// specified context.
        /// </summary>
        /// <param name="context">The instance of the type descriptor context.</param>
        /// <param name="value">The instance of the object to be validated.</param>
        /// <returns>a boolean represents whether the given value is valid or not.</returns>
        public override bool IsValid(ITypeDescriptorContext context, object value)
        {
            // query from the interface
            return this.GetIsValidFromContext(context, value);
        }

        #region Private Utilites

        /// <summary>
        /// Gets the context information for querying the standard value provider
        /// </summary>
        /// <param name="propertyDescriptor">the instance of the MergedPropertyDescriptor</param>
        /// <returns>A generic dictionary, called context info, containing
        /// the key value pair of IStandardValueProvider and the domain property id
        /// for querying the standard values.
        /// </returns>
        private Dictionary<IStandardValueProvider, Guid> GetContextInfo(PropertyDescriptor propertyDescriptor)
        {
            // context to return
            Dictionary<IStandardValueProvider, Guid> contextInfo = new Dictionary<IStandardValueProvider, Guid>();

            // get the value of "descriptors" field
            // usually, this will be array of propery descirptors.
            // this is an extension method written for PVE10
            PropertyDescriptor[] propertyDescriptors
                = propertyDescriptor.GetFieldValue("descriptors") as PropertyDescriptor[];

            // of course, this is an array!
            if (propertyDescriptors != null)
            {
                // foreach iteration
                propertyDescriptors.ForEach
                (
                    // object :'(
                    item =>
                    {
                        // null check and type cast
                        if (item != null
                            && item is ElementPropertyDescriptor)
                        {
                            // get the standardValueProvider descriptor
                            ElementPropertyDescriptor descriptor = item as ElementPropertyDescriptor;

                            // get the standard value provider
                            IStandardValueProvider standardValueProvider = descriptor.GetStandardValueProvider();

                            if (standardValueProvider != null)
                            {
                                // already there in the context??
                                if (!contextInfo.ContainsKey(standardValueProvider))
                                {
                                    // add
                                    contextInfo.Add(standardValueProvider, Guid.Empty);
                                }
                                // not the current domain property id?
                                if (!contextInfo[standardValueProvider].Equals(descriptor.DomainPropertyInfo.Id))
                                {
                                    // update!
                                    contextInfo[standardValueProvider] = descriptor.DomainPropertyInfo.Id;
                                }
                            }
                        }
                    }
                );
            }

            // return the context
            return contextInfo;
        }

        /// <summary>
        /// Reflects the context and gets the context information
        /// </summary>
        /// <param name="context">type descriptor context as ITypeDescriptorContext</param>
        /// <returns>a Dictionary<IStandardValueProvider, Guid>, called the context information
        /// contains the KVP standard value provider and the property for querying the standard value.</returns>
        private Dictionary<IStandardValueProvider, Guid> ReflectContextInfo(ITypeDescriptorContext context)
        {
            // context info
            Dictionary<IStandardValueProvider, Guid> contextInfo
                = new Dictionary<IStandardValueProvider, Guid>();

            // null check
            if (context != null)
            {
                // type verification for Merged Property Descriptor
                if (!(context.PropertyDescriptor is ElementPropertyDescriptor))
                {
                    // generate the context
                    contextInfo = this.GetContextInfo(context.PropertyDescriptor);
                }
                else
                {
                    // type cast
                    ElementPropertyDescriptor descriptor = context.PropertyDescriptor as ElementPropertyDescriptor;

                    // get the standard value provider
                    IStandardValueProvider standardValueProvider = descriptor != null
                        ? descriptor.GetStandardValueProvider()
                        : null;

                    // null check
                    if (standardValueProvider != null)
                    {
                        // not in context?
                        if (!contextInfo.ContainsKey(standardValueProvider))
                        {
                            // then add it
                            contextInfo.Add(standardValueProvider, Guid.Empty);
                        }
                        // not the current domain property id?
                        if (!contextInfo[standardValueProvider].Equals(descriptor.DomainPropertyInfo.Id))
                        {
                            // then update it!
                            contextInfo[standardValueProvider] = descriptor.DomainPropertyInfo.Id;
                        }
                    }
                }
            }

            // return context
            return contextInfo;
        }

        /// <summary>
        /// Gets whether the given list is an exclusive list or not from context
        /// </summary>
        /// <param name="context">instance of the ITypeDescriptorContext</param>
        /// <returns>boolean states whether the given list is an exclusive list or not from context or not</returns>
        private bool GetStandardValuesExclusiveFromContext(ITypeDescriptorContext context)
        {
            // is exclusive collection?
            bool isExclusive = false;

            // context information for query
            Dictionary<IStandardValueProvider, Guid> contextInfo = this.ReflectContextInfo(context);

            // null check
            if (contextInfo != null && contextInfo.Count > 0)
            {
                // binary AND initialization
                isExclusive = true;

                // foreach iteration
                foreach (KeyValuePair<IStandardValueProvider, Guid> kvp in contextInfo)
                {
                    // oops!
                    if (kvp.Key == null
                        || kvp.Value.Equals(Guid.Empty))
                    {
                        isExclusive = false;
                    }

                    else
                    {
                        // Bitwise AND with the standard value supported by provider
                        isExclusive &= kvp.Key.GetStandardValuesExclusive(kvp.Value);
                    }

                    // break if atleast one provider does not support std value
                    if (!isExclusive)
                        break;
                }
            }
            else
            {
                // default ;)
                isExclusive = base.GetStandardValuesExclusive(context);
            }

            //return
            return isExclusive;

        }

        /// <summary>
        /// Gets the collection of standard type of values queried from the the collection provider.
        /// </summary>
        /// <param name="context">instance of the ITypeDescriptorContext</param>
        /// <returns>A collection of standard set of strings queried from the collection provider.</returns>
        private StandardValuesCollection GetStandardValuesFromContext(ITypeDescriptorContext context)
        {
            StandardValuesCollection values = null;         // standard set of values to be returned
            List<string> colValues = null;                  // collection of values returned by the collection provider

            // get the context information
            Dictionary<IStandardValueProvider, Guid> contextInfo = this.ReflectContextInfo(context);

            // null check
            if (contextInfo != null && contextInfo.Count > 0)
            {
                // initialize the collection to null
                colValues = null;

                // Consolidated Set Operation
                SetOperation combinedOperation = SetOperation.None;

                // foreach iteration
                foreach (KeyValuePair<IStandardValueProvider, Guid> kvp in contextInfo)
                {
                    // null check
                    if (kvp.Key == null
                        || kvp.Value.Equals(Guid.Empty))
                    {
                        continue;
                    }
                    else
                    {
                        // instantiates a fresh list
                        List<string> queriedValues = new List<string>();

                        // temporary set individualOperation for merging.
                        SetOperation individualOperation = SetOperation.None;

                        // query the standard values and append our collection

                        queriedValues.AddRange(kvp.Key.GetStandardValues(kvp.Value, ref individualOperation)
                            ?? new List<string>());

                        // update the collection if null
                        if (colValues == null)
                        {
                            colValues = queriedValues;
                        }

                        // combine the set operation
                        combinedOperation = combinedOperation.Merge(individualOperation); 

                        // perform set operation for merging
                        colValues = SetOperation.Union.Equals(combinedOperation)
                            || SetOperation.None.Equals(combinedOperation)
                            ? colValues.Union(queriedValues).ToList()
                            : colValues.Intersect(queriedValues).ToList();

                        queriedValues.Clear();
                        queriedValues = null;
                    }
                }
                // null check
                if (colValues != null
                    && colValues.Count > 0)
                {
                    // take the distinct items
                    colValues = colValues.Distinct().ToList();
                    // sort the list
                    colValues.Sort();
                    // generate the standard values
                    values = new StandardValuesCollection(colValues);
                }

            }
            else
            {
                // default
                values = base.GetStandardValues(context);
            }

            // cleanup
            colValues = null;

            //return
            return values;
        }

        /// <summary>
        /// Gets whether the standard values are supported for the given context
        /// </summary>
        /// <param name="context">instance of the ITypeDescriptorContext</param>
        /// <returns>A boolean states whether the standard values are supported for the given context or not</returns>
        private bool GetStandardValuesSupportedFromContext(ITypeDescriptorContext context)
        {
            // is supported?
            bool isSupported = false;

            // query context info
            Dictionary<IStandardValueProvider, Guid> contextInfo = this.ReflectContextInfo(context);

            // null check
            if (contextInfo != null && contextInfo.Count > 0)
            {
                // initialize to true for bitwise AND
                isSupported = true;

                // foreach iteration
                foreach (KeyValuePair<IStandardValueProvider, Guid> kvp in contextInfo)
                {
                    // null check
                    if (kvp.Key == null
                        || kvp.Value.Equals(Guid.Empty))
                    {
                        isSupported = false;
                    }

                    else
                    {
                        // bitwise AND with queried values
                        isSupported &= kvp.Key.GetStandardValuesSupported(kvp.Value);
                    }

                    // break when the first false comes :D
                    if (!isSupported)
                        break;
                }
            }
            else
            {
                // default
                isSupported = base.GetStandardValuesSupported(context);
            }

            //return
            return isSupported;
        }

        /// <summary>
        /// Checks whether the given value is valid for the specific context or not.
        /// </summary>
        /// <param name="context">instance of the ITypeDescriptorContext</param>
        /// <param name="value">A boolean states whether the selected value is valid for the given context or not</param>
        /// <returns></returns>
        private bool GetIsValidFromContext(ITypeDescriptorContext context, object value)
        {
            // is valid?
            bool isValid = false;

            // query context info
            Dictionary<IStandardValueProvider, Guid> contextInfo = this.ReflectContextInfo(context);

            // null check
            if (contextInfo != null && contextInfo.Count > 0)
            {
                // initialize to true for bitwise AND
                isValid = true;

                // foreach iteration
                foreach (KeyValuePair<IStandardValueProvider, Guid> kvp in contextInfo)
                {
                    // null check
                    if (kvp.Key == null
                        || kvp.Value.Equals(Guid.Empty))
                    {
                        isValid = false;
                    }

                    else
                    {
                        // bitwise AND with queried values
                        isValid &= kvp.Key.ValidateString(kvp.Value, value as string);
                    }

                    // break when the first false comes :D
                    if (!isValid)
                        break;
                }
            }
            else
            {
                // default
                isValid = base.IsValid(context);
            }

            //return
            return isValid;
        }

        #endregion

    }

}
